<template>
  <div class="jars-container" v-loading="loading">
    <!-- 页面标题和操作按钮 -->
    <div class="page-header">
      <div class="header-background">
        <div class="bg-pattern"></div>
        <div class="bg-gradient"></div>
      </div>
      
      <div class="page-title">
        <div class="title-icon">
          <div class="icon-container" style="width: 36px; height: 36px; background: linear-gradient(135deg, #409eff 0%, #66b3ff 100%); border-radius: 8px; color: white; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);">
            <el-icon size="20"><List /></el-icon>
            <div class="icon-glow"></div>
          </div>
        </div>
        <div class="title-content">
          <div class="title-main">
            <h1>JAR包管理</h1>
            <div class="title-badge">
              <el-icon><Star /></el-icon>
              <span>Data</span>
            </div>
          </div>
          <p class="page-description">
            <el-icon><Monitor /></el-icon>
            管理和监控Data JAR包的上传、部署和运行状态
          </p>
          <div class="title-meta">
            <div class="meta-item">
              <el-icon><Clock /></el-icon>
              <span>实时监控</span>
            </div>
            <div class="meta-item">
              <el-icon><Lock /></el-icon>
              <span>安全上传</span>
            </div>
            <div class="meta-item">
              <el-icon><Menu /></el-icon>
              <span>版本管理</span>
            </div>
          </div>
        </div>
      </div>
      
      <div class="page-stats">
        <div class="stat-card total">
          <div class="stat-icon">
            <el-icon><Document /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-number">{{ totalAll || total || jars.length }}</div>
            <div class="stat-label">JAR包总数</div>
            <div class="stat-trend">
              <el-icon><Menu /></el-icon>
              <span>全部</span>
            </div>
          </div>
        </div>
        
        <div class="stat-card uploaded">
          <div class="stat-icon">
            <el-icon><Check /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-number">{{ getUploadedCount() }}</div>
            <div class="stat-label">已上传</div>
            <div class="stat-trend">
              <el-icon><Check /></el-icon>
              <span>可用</span>
            </div>
          </div>
        </div>
      </div>
    </div>


    <!-- 搜索和筛选区域 -->
    <div class="search-section">
      <el-card shadow="hover" class="search-card">
        <template #header>
          <div class="search-header">
            <div class="search-title">
              <el-icon><Search /></el-icon>
              <span>搜索与筛选</span>
            </div>
            <!-- Flink环境选择器 -->
            <div class="instance-selector-compact">
              <el-select 
                v-model="appStore.currentFlinkEnv" 
                placeholder="选择Flink环境"
                @change="handleEnvironmentChange"
                size="small"
                style="width: 280px"
              >
                <el-option
                  v-for="env in appStore.flinkEnvironments"
                  :key="env.name"
                  :label="`${env.name} (${env.host}:${env.port})`"
                  :value="env.name"
                >
                  <div style="display: flex; justify-content: space-between; align-items: center;">
                    <span style="font-weight: 500;">{{ env.name }}</span>
                    <span style="color: #909399; font-size: 12px; margin-left: 20px;">
                      {{ env.host }}:{{ env.port }}
                    </span>
                  </div>
                </el-option>
              </el-select>
              
              <el-tag v-if="appStore.currentFlinkEnv" type="success" size="small">
                <el-icon><Connection /></el-icon>
                {{ appStore.currentFlinkEnv }}
              </el-tag>
            </div>
          </div>
        </template>
        <el-form :model="searchForm" class="search-form">
          <div class="search-form-content">
            <div class="search-form-fields">
              <el-form-item label="JAR名称">
                <el-input
                  v-model="searchForm.name"
                  placeholder="请输入JAR名称"
                  clearable
                  style="width: 200px"
                />
              </el-form-item>
            </div>
            
            <div class="search-form-actions">
              <el-button type="primary" @click="handleSearch" class="search-btn">
                <el-icon><Search /></el-icon>
                搜索
              </el-button>
              <el-button type="warning" @click="handleReset" class="reset-btn">
                <el-icon><Refresh /></el-icon>
                重置
              </el-button>
              <el-button type="success" @click="showUploadDialog = true" class="upload-btn">
                <el-icon><Plus /></el-icon>
                上传JAR
              </el-button>
            </div>
          </div>
        </el-form>
      </el-card>
    </div>

    <!-- 批量操作区域 -->
    <div class="batch-actions-bar" v-if="selectedJars.length > 0">
      <div class="batch-info-compact">
        <el-icon class="selected-icon"><Select /></el-icon>
        <span>已选择<strong>{{ selectedJars.length }}</strong>个JAR包</span>
      </div>
      <div class="batch-divider">|</div>
      <div class="batch-buttons-compact">
        <el-button
          type="danger"
          class="batch-btn-compact"
          @click="showBulkDeleteDialog = true"
        >
          <el-icon><Delete /></el-icon>
          批量删除
        </el-button>
      </div>
    </div>

    <!-- JAR包列表 -->
    <div class="table-section" style="position: relative;">
      <el-card shadow="hover" class="table-card">
        <template #header>
          <div class="table-header">
            <div class="table-title">
              <el-icon><List /></el-icon>
              <span>JAR包列表</span>
            </div>
            <div class="table-header-actions">
              <el-button type="success" @click="handleRefresh" class="refresh-btn" :loading="loading" size="small">
                <el-icon><Refresh /></el-icon>
                刷新
              </el-button>
              <div class="table-info">
                <div class="pagination-pill">
                  <span class="page-count">本页 {{ filteredJars.length }} 条</span>
                </div>
              </div>
            </div>
          </div>
        </template>
        <div class="table-container">
          <el-table
            :data="filteredJars"
            :default-sort="{ prop: 'uploaded', order: 'descending' }"
            @selection-change="handleSelectionChange"
            @sort-change="handleSortChange"
            style="width: 100%;"
            :row-key="(row, index) => row.id || index"
            :height="600"
            :virtual-scrolling="false"
            :virtual-scroll-item-size="50"
            table-layout="fixed"
            :row-class-name="getRowClassName"
            class="enhanced-table standard-data-table"
          >
            <el-table-column type="selection" width="55" />
            
            <!-- JAR名称列 -->
            <el-table-column prop="name" label="JAR名称" min-width="200" show-overflow-tooltip>
              <template #default="{ row }">
                <div class="jar-name copyable" @click="copyToClipboard(row.name, 'JAR名称')">
                  <div class="jar-info">
                    <div class="jar-title">{{ row.name }}</div>
                  </div>
                  <div class="copy-hint">
                    <el-icon><Document /></el-icon>
                  </div>
                </div>
              </template>
            </el-table-column>
            
            <!-- JAR ID列 -->
            <el-table-column prop="id" label="JAR ID" min-width="160" show-overflow-tooltip>
              <template #default="{ row }">
                <div class="copyable-field" @click="copyToClipboard(row.id, 'JAR ID')">
                  <el-text type="info">{{ row.id || '-' }}</el-text>
                  <div class="copy-hint">
                    <el-icon><Document /></el-icon>
                  </div>
                </div>
              </template>
            </el-table-column>
            
            <!-- 入口类列 -->
            <el-table-column prop="entry" label="入口类" min-width="200" show-overflow-tooltip>
              <template #default="{ row }">
                <div v-if="row.entry && row.entry !== '-'" class="copyable-field" @click="copyToClipboard(row.entry, '入口类')">
                  <el-text>{{ row.entry }}</el-text>
                  <div class="copy-hint">
                    <el-icon><Document /></el-icon>
                  </div>
                </div>
                <el-text v-else size="small">-</el-text>
              </template>
            </el-table-column>
            
            <!-- 上传时间列 -->
            <el-table-column 
              prop="uploaded" 
              label="上传时间" 
              min-width="180" 
              sortable="custom"
              :sort-orders="['descending', 'ascending', null]"
              show-overflow-tooltip
            >
              <template #default="{ row }">
                <el-text>{{ formatTime(row.uploaded) }}</el-text>
              </template>
            </el-table-column>
            
            <!-- 操作列 -->
            <el-table-column label="操作" width="220" fixed="right">
              <template #default="{ row }">
                <div class="action-buttons">
                  <el-tooltip content="查看详情" placement="top">
                    <el-button
                      type="primary"
                      size="small"
                      @click="handleViewDetails(row)"
                      circle
                    >
                      <el-icon><View /></el-icon>
                    </el-button>
                  </el-tooltip>
                  <el-tooltip content="删除JAR包" placement="top">
                    <el-button
                      type="danger"
                      size="small"
                      @click="handleDeleteJar(row)"
                      circle
                    >
                      <el-icon><Delete /></el-icon>
                    </el-button>
                  </el-tooltip>
                </div>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-card>
    </div>

    <!-- JAR上传对话框 -->
    <el-dialog
      v-model="showUploadDialog"
      width="640px"
      top="6vh"
      :append-to-body="true"
      :close-on-click-modal="false"
      class="unified-dialog jar-upload-dialog"
    >
      <template #header>
        <DialogHeader
          icon="Plus"
          title="上传JAR包"
          description="选择Flink环境并上传JAR包文件"
          type="primary"
        />
      </template>
      
      <!-- 环境选择区域 -->
      <div class="upload-env-section">
        <el-form :model="uploadForm" label-width="100px" class="upload-form">
          <el-form-item label="Flink环境" required>
            <el-select
              v-model="uploadForm.flinkEnv"
              placeholder="请选择Flink环境"
              style="width: 100%"
              @change="handleUploadEnvChange"
            >
              <el-option
                v-for="env in appStore.flinkEnvironments"
                :key="env.name"
                :label="`${env.name} (${env.host}:${env.port})`"
                :value="env.name"
              >
                <div class="env-option">
                  <div class="env-name">{{ env.name }}</div>
                  <div class="env-url">{{ env.host }}:{{ env.port }}</div>
                </div>
              </el-option>
            </el-select>
          </el-form-item>
          
          <el-form-item label="上传选项">
            <div class="upload-options">
              <el-checkbox
                v-model="uploadForm.cleanFirst"
                :disabled="!uploadForm.flinkEnv"
              >
                上传前删除所有重名已存在的JAR包
              </el-checkbox>
              <div class="option-tip">
                <el-icon><InfoFilled /></el-icon>
                <span>开启此选项将先删除目标环境中与要上传文件同名的JAR包，再上传新文件</span>
              </div>
            </div>
          </el-form-item>
        </el-form>
      </div>
      
      <!-- 文件上传和管理区域 -->
      <div class="file-upload-section">
        <div class="section-header">
          <div class="header-left">
            <el-icon><Document /></el-icon>
            <span>JAR文件管理</span>
            <span v-if="tempFileList.length > 0" class="file-count">({{ tempFileList.length }})</span>
          </div>
          <div class="header-actions" v-if="tempFileList.length > 0">
            <el-button size="small" @click="selectAllFiles">全选</el-button>
            <el-button size="small" @click="unselectAllFiles">取消全选</el-button>
            <el-button size="small" type="danger" @click="clearTempFiles">清空列表</el-button>
          </div>
        </div>
        
        <div class="upload-area">
          <div class="upload-buttons-group">
            <!-- 选择文件按钮 -->
            <el-upload
              ref="uploadRef"
              :action="tempUploadUrl"
              :before-upload="beforeUpload"
              :on-progress="handleUploadProgress"
              :on-success="handleUploadSuccess"
              :on-error="handleUploadError"
              :file-list="tempFileList"
              accept=".jar"
              multiple
              name="jarfiles"
              :disabled="!uploadForm.flinkEnv"
              :auto-upload="true"
              :show-file-list="false"
            >
              <el-button type="primary" :disabled="!uploadForm.flinkEnv">
                <el-icon><Document /></el-icon>
                选择JAR文件
              </el-button>
            </el-upload>
            
            <!-- 选择本地目录按钮 -->
            <div class="directory-upload-wrapper">
              <input
                ref="directoryInput"
                type="file"
                webkitdirectory
                directory
                multiple
                accept=".jar"
                @change="handleDirectorySelect"
                style="display: none"
              />
              <el-button 
                type="success" 
                :disabled="!uploadForm.flinkEnv"
                @click="triggerDirectorySelect"
              >
                <el-icon><Folder /></el-icon>
                选择本地目录
              </el-button>
            </div>
          </div>
          
          <div class="upload-tip">
            <el-icon><InfoFilled /></el-icon>
            <div class="tip-content">
              <p><strong>上传流程说明：</strong></p>
              <p>1️⃣ <strong>第一步：上传到服务器</strong> - 选择文件/目录后，文件会先上传到服务器临时存储</p>
              <p>2️⃣ <strong>第二步：同步到Flink</strong> - 上传完成后，点击下方"同步到Flink"按钮，才会真正部署到Flink集群</p>
              <p style="color: #e6a23c;">⚠️ 只有完成两步操作，JAR包才算真正部署完成！</p>
            </div>
            <br>
            <span v-if="!uploadForm.flinkEnv" style="color: #f56c6c;">
              请先选择Flink环境
            </span>
          </div>
        </div>
        
        <!-- 文件列表提示 -->
        <el-alert
          v-if="tempFileList.length > 0"
          type="warning"
          :closable="false"
          show-icon
          class="file-list-alert"
        >
          <template #title>
            <span style="font-weight: 600;">📋 临时文件列表（尚未部署到Flink）</span>
          </template>
          <template #default>
            <p style="margin: 0;">当前有 <strong>{{ tempFileList.length }}</strong> 个文件已上传到服务器临时存储。</p>
            <p style="margin: 4px 0 0 0;">请勾选需要部署的文件，然后点击下方 <strong>"同步到Flink"</strong> 按钮完成部署。</p>
          </template>
        </el-alert>
        
        <!-- 文件列表 -->
        <div v-if="tempFileList.length > 0" class="files-list">
          <div 
            v-for="(file, index) in tempFileList" 
            :key="index"
            class="file-item"
            :class="{ 
              'uploading': file.status === 'uploading', 
              'success': file.status === 'success',
              'selected': file.selected,
              'registering': file.registering
            }"
          >
            <div class="file-checkbox">
              <el-checkbox 
                v-model="file.selected" 
                :disabled="file.status !== 'success' || file.registering"
              />
            </div>
            <div class="file-info">
              <el-icon><Document /></el-icon>
              <span class="file-name">{{ file.name }}</span>
              <span class="file-status">
                <el-icon v-if="file.status === 'uploading'"><Loading /></el-icon>
                <el-icon v-else-if="file.status === 'success' && !file.registering" color="#67c23a"><Check /></el-icon>
                <el-icon v-else-if="file.registering" color="#409eff"><Loading /></el-icon>
                <el-icon v-else-if="file.status === 'fail'" color="#f56c6c"><Close /></el-icon>
              </span>
            </div>
            <div class="file-actions">
              <el-button 
                v-if="file.status === 'success' && !file.registering"
                type="danger" 
                size="small" 
                @click="removeFile(index)"
              >
                <el-icon><Delete /></el-icon>
              </el-button>
            </div>
          </div>
        </div>
      </div>
      
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="showUploadDialog = false">取消</el-button>
          <el-button 
            type="primary" 
            @click="submitToFlink" 
            :loading="isUploading"
            :disabled="selectedFileCount === 0 || !uploadForm.flinkEnv"
          >
            <el-icon><Upload /></el-icon>
            同步到Flink ({{ selectedFileCount }})
          </el-button>
        </div>
      </template>
    </el-dialog>

    <!-- 批量删除对话框 -->
    <el-dialog
      v-model="showBulkDeleteDialog"
      width="520px"
      top="8vh"
      :append-to-body="true"
      :close-on-click-modal="false"
      class="unified-dialog"
    >
      <template #header>
        <DialogHeader
          icon="Warning"
          title="批量删除确认"
          :description="`删除选中的${selectedJars.length}个JAR包`"
          type="danger"
        />
      </template>
      
      <div class="dialog-content">
        <el-alert
          title="⚠️ 危险操作"
          type="error"
          :closable="false"
          show-icon
        >
          <template #default>
            <p style="margin: 8px 0;">此操作将删除选中的 <strong>{{ selectedJars.length }}</strong> 个JAR包！</p>
            <p style="margin: 8px 0 0 0;">此操作<strong>不可撤销</strong>，请谨慎操作！</p>
          </template>
        </el-alert>
      </div>
      
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="showBulkDeleteDialog = false">取消</el-button>
          <el-button type="danger" @click="confirmBulkDelete" :loading="isDeleting">
            <el-icon><Delete /></el-icon>
            确认删除
          </el-button>
        </div>
      </template>
    </el-dialog>

    <!-- JAR详情对话框 -->
    <JarDetailDialog
      v-model="showDetailDialog"
      :jar="currentJar"
      @delete="handleDeleteJar"
    />

    <!-- 批量进度对话框 -->
    <BulkProgressDialog
      ref="bulkProgressRef"
      v-model="showBulkProgressDialog"
      title="JAR包上传进度"
      subtitle="正在上传JAR包到Flink集群"
      @background="handleBackgroundTask"
      @cancel="cancelUploadTask"
    />

    <!-- 后台任务浮动按钮 - 右侧边栏样式 -->
    <transition name="slide-fade">
      <div
        v-if="hasBackgroundTask"
        class="background-task-indicator"
        :class="{ collapsed: !isSidebarExpanded }"
        :title="isSidebarExpanded ? '单击收起，双击查看详情' : '单击展开'"
      >
        <!-- 收起状态：只显示箭头 -->
        <div 
          v-if="!isSidebarExpanded" 
          class="arrow-icon"
          @click="isSidebarExpanded = true"
        >
          <el-icon><DArrowLeft /></el-icon>
        </div>
        
        <!-- 展开状态：显示完整内容 -->
        <div 
          v-else
          class="task-content-wrapper"
          @click="handleSingleClick"
          @dblclick="handleDoubleClick"
        >
          <div class="task-icon">
            <el-icon v-if="backgroundTaskCompleted" class="icon-success"><CircleCheck /></el-icon>
            <el-icon v-else class="icon-loading"><Loading /></el-icon>
          </div>
          <div class="task-text">
            <div class="task-label">{{ backgroundTaskCompleted ? '已完成' : '进行中' }}</div>
            <div class="task-progress">{{ backgroundTaskProgress }}</div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { CircleCheck, Loading, Rank, FullScreen, DArrowLeft, Connection } from '@element-plus/icons-vue'
import { storeToRefs } from 'pinia'
import { componentLogger } from '@/utils/logger'
import { copyToClipboard as copyText } from '@/utils/clipboard'
import dayjs from 'dayjs'
import { useAppStore } from '@/stores/app'
import { useBackgroundTaskStore } from '@/stores/backgroundTask'
import DialogHeader from '@/components/DialogHeader.vue'
import JarDetailDialog from './components/JarDetailDialog.vue'
import BulkProgressDialog from './components/BulkProgressDialog.vue'
import api from '@/utils/api'

const appStore = useAppStore()
const backgroundTaskStore = useBackgroundTaskStore()
const { sidebarCollapsed } = storeToRefs(appStore)

// 响应式数据
const jars = ref([])
const loading = ref(false)
const total = ref(0)  // JAR包总数
const totalAll = ref(0)  // 全局总数（用于统计卡片）
const selectedJars = ref([])
const showUploadDialog = ref(false)
const showBulkDeleteDialog = ref(false)
const showDetailDialog = ref(false)
const currentJar = ref(null)
const isUploading = ref(false)
const isDeleting = ref(false)
const fileList = ref([])

// 搜索表单
const searchForm = ref({
  name: ''
})

// 排序状态（默认按上传时间降序）
const sortField = ref('uploaded')
const sortOrder = ref('desc')

// 上传表单
const uploadForm = ref({
  flinkEnv: '',
  cleanFirst: true
})

// 临时文件列表
const tempFileList = ref([])

// 目录选择引用
const directoryInput = ref(null)

// 批量进度对话框
const showBulkProgressDialog = ref(false)
const bulkProgressRef = ref(null)

// 后台任务状态
const hasBackgroundTask = ref(false)
const backgroundTaskProgress = ref(0)
const backgroundTaskCompleted = ref(false)
const currentTaskId = ref(null)  // 当前任务ID，用于取消
const currentTaskFiles = ref([])  // 当前任务的文件列表，用于重新打开对话框

// 侧边栏展开/收起状态
const isSidebarExpanded = ref(true)
const clickTimer = ref(null)

// 计算属性
const currentFlinkEnv = computed(() => {
  const envConfig = appStore.getCurrentFlinkEnvConfig()
  if (!envConfig) return ''
  
  // 构建完整的URL
  const protocol = envConfig.protocol || 'http'
  const host = envConfig.host || ''
  const port = envConfig.port ? `:${envConfig.port}` : ''
  const path = envConfig.path || ''
  return `${protocol}://${host}${port}${path}`
})
// 临时上传URL（上传到服务器）
const tempUploadUrl = computed(() => {
  let url = '/api/jars/upload-temp'
  const params = []
  
  // 如果上传表单中选择了环境，使用上传表单的环境
  if (uploadForm.value.flinkEnv) {
    const envConfig = appStore.flinkEnvironments.find(env => env.name === uploadForm.value.flinkEnv)
    if (envConfig) {
      const protocol = envConfig.protocol || 'http'
      const host = envConfig.host || ''
      const port = envConfig.port ? `:${envConfig.port}` : ''
      const path = envConfig.path || ''
      params.push(`flink_env=${encodeURIComponent(`${protocol}://${host}${port}${path}`)}`)
    }
  } else {
    // 否则使用当前页面选择的环境
    params.push(`flink_env=${encodeURIComponent(currentFlinkEnv.value)}`)
  }
  
  if (params.length > 0) {
    url += '?' + params.join('&')
  }
  
  return url
})
const filteredJars = computed(() => {
  if (!searchForm.value.name) {
    return jars.value
  }
  return jars.value.filter(jar => 
    jar.name && jar.name.toLowerCase().includes(searchForm.value.name.toLowerCase())
  )
})

// 选中的文件数量
const selectedFileCount = computed(() => {
  return tempFileList.value.filter(file => file.selected && file.status === 'success').length
})

// 方法
const fetchJars = async () => {
  try {
    loading.value = true
    componentLogger.info('Fetching JARs from:', currentFlinkEnv.value)
    const url = `/api/jars?flink_env=${encodeURIComponent(currentFlinkEnv.value)}&sort=${sortField.value}&order=${sortOrder.value}`
    const response = await fetch(url)
    const data = await response.json()
    componentLogger.info('JAR API response:', data)
    
    if (data.success && data.data && data.data.jars) {
      jars.value = data.data.jars
      // 提取统计数据
      total.value = data.data.total || jars.value.length
      totalAll.value = data.data.total_all || jars.value.length
      componentLogger.info('Loaded JARs:', jars.value.length, 'Total:', total.value, 'TotalAll:', totalAll.value)
      // 调试：查看第一个 JAR 的完整数据结构
      if (jars.value.length > 0) {
        componentLogger.info('First JAR data structure:', jars.value[0])
        componentLogger.info('Available fields:', Object.keys(jars.value[0]))
      }
    } else if (data.error) {
      componentLogger.error('API returned error:', data.error)
      ElMessage.error(`获取JAR列表失败: ${data.error}`)
    } else {
      componentLogger.warn('No JARs data in response')
      jars.value = []
    }
  } catch (error) {
    componentLogger.error('获取JAR列表失败:', error)
    ElMessage.error('获取JAR列表失败')
  } finally {
    loading.value = false
  }
}

const handleSearch = () => {
  // 搜索逻辑已在computed中实现
}

const handleReset = () => {
  searchForm.value.name = ''
}

const handleEnvironmentChange = () => {
  // 环境切换时保存选择并重新获取JAR列表
  appStore.setCurrentFlinkEnv(appStore.currentFlinkEnv)
  fetchJars()
}

const handleRefresh = () => {
  // 刷新JAR列表
  fetchJars()
}

// 处理排序变化
const handleSortChange = ({ prop, order }) => {
  if (!prop || !order) {
    // 取消排序，恢复默认（按上传时间降序）
    sortField.value = 'uploaded'
    sortOrder.value = 'desc'
  } else {
    // 设置排序
    sortField.value = prop
    sortOrder.value = order === 'ascending' ? 'asc' : 'desc'
  }
  // 刷新数据
  fetchJars()
}

const handleUploadEnvChange = () => {
  // 上传环境切换时的处理逻辑
  componentLogger.info('上传环境切换到:', uploadForm.value.flinkEnv)
}

const handleSelectionChange = (selection) => {
  selectedJars.value = selection
}

const getUploadedCount = () => {
  // 使用全局统计数据，如果没有则降级使用当前数据长度
  return totalAll.value || total.value || jars.value.length
}

const formatTime = (time) => {
  if (!time) return '-'
  return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
}

// 入口类解析逻辑已移至后端处理


const getRowClassName = ({ row, rowIndex }) => {
  return 'enhanced-row'
}

// 复制到剪贴板 - 使用统一工具
const copyToClipboard = async (text, fieldName) => {
  await copyText(text, fieldName)
}

const handleViewDetails = (jar) => {
  currentJar.value = jar
  showDetailDialog.value = true
}


const handleDeleteJar = async (jar) => {
  // 如果是从详情对话框触发的删除，不再显示确认对话框（因为详情对话框中已经有确认对话框）
  const isFromDialog = showDetailDialog.value && currentJar.value?.id === jar.id
  
  try {
    // 只有从表格操作列触发时才显示确认对话框
    if (!isFromDialog) {
      await ElMessageBox.confirm(
        `确定要删除JAR包 "${jar.name}" 吗？此操作不可恢复！`,
        '确认删除',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'error'
        }
      )
    }
    
    const response = await fetch(`/api/jars/${jar.id}/delete`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        flink_env: currentFlinkEnv.value
      })
    })
    
    const result = await response.json()
    if (response.ok) {
      ElMessage.success('JAR包删除成功')
      // 如果是从详情对话框触发的删除，关闭对话框
      if (isFromDialog) {
        showDetailDialog.value = false
      }
      fetchJars()
    } else {
      ElMessage.error(result.error || '删除失败')
    }
  } catch (error) {
    if (error !== 'cancel') {
      componentLogger.error('删除JAR包失败:', error)
      ElMessage.error('删除失败')
    }
  }
}

const confirmBulkDelete = async () => {
  try {
    isDeleting.value = true
    
    const deletePromises = selectedJars.value.map(jar => 
      fetch(`/api/jars/${jar.id}/delete`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          flink_env: currentFlinkEnv.value
        })
      })
    )
    
    const results = await Promise.allSettled(deletePromises)
    const successCount = results.filter(result => result.status === 'fulfilled' && result.value.ok).length
    const failCount = results.length - successCount
    
    if (successCount > 0) {
      ElMessage.success(`成功删除 ${successCount} 个JAR包`)
    }
    if (failCount > 0) {
      ElMessage.warning(`${failCount} 个JAR包删除失败`)
    }
    
    showBulkDeleteDialog.value = false
    selectedJars.value = []
    fetchJars()
  } catch (error) {
    componentLogger.error('批量删除失败:', error)
    ElMessage.error('批量删除失败')
  } finally {
    isDeleting.value = false
  }
}

const beforeUpload = (file) => {
  const isJar = file.type === 'application/x-java-archive' || file.name.endsWith('.jar')

  if (!isJar) {
    ElMessage.error('只能上传JAR文件!')
    return false
  }
  return true
}

// 触发目录选择
const triggerDirectorySelect = () => {
  if (!uploadForm.value.flinkEnv) {
    ElMessage.warning('请先选择Flink环境')
    return
  }
  directoryInput.value?.click()
}

// 处理目录选择
const handleDirectorySelect = async (event) => {
  const files = event.target.files
  if (!files || files.length === 0) {
    return
  }
  
  componentLogger.info(`选择了目录，共 ${files.length} 个文件`)
  
  // 过滤符合条件的JAR文件
  const filteredFiles = []
  for (let i = 0; i < files.length; i++) {
    const file = files[i]
    const filename = file.name
    const relativePath = file.webkitRelativePath || ''
    
    // 过滤规则：只上传 yc-*-SNAPSHOT.jar，排除 original- 开头和 yc-flink-common
    const shouldUpload = (
      filename.endsWith('.jar') &&
      filename.startsWith('yc-') &&
      filename.endsWith('-SNAPSHOT.jar') &&
      !filename.startsWith('original-') &&
      !relativePath.includes('yc-flink-common')
    )
    
    if (shouldUpload) {
      filteredFiles.push(file)
      componentLogger.info(`准备上传: ${filename}`)
    } else {
      componentLogger.info(`跳过文件: ${filename}`)
    }
  }
  
  if (filteredFiles.length === 0) {
    ElMessage.warning('未找到符合条件的JAR文件')
    // 重置input
    event.target.value = ''
    return
  }
  
  ElMessage.success({
    message: `找到 ${filteredFiles.length} 个符合条件的JAR文件，开始上传到服务器...`,
    duration: 3000
  })
  
  // 使用FormData上传文件
  const formData = new FormData()
  filteredFiles.forEach(file => {
    formData.append('jarfiles', file)
  })
  
  try {
    // 构建上传URL
    const envConfig = appStore.flinkEnvironments.find(env => env.name === uploadForm.value.flinkEnv)
    let fullFlinkUrl = uploadForm.value.flinkEnv
    if (envConfig && envConfig.host) {
      const protocol = envConfig.protocol || 'http'
      const host = envConfig.host || ''
      const port = envConfig.port ? `:${envConfig.port}` : ''
      const path = envConfig.path || ''
      fullFlinkUrl = `${protocol}://${host}${port}${path}`
    }
    
    const uploadUrl = `/api/jars/upload-temp?flink_env=${encodeURIComponent(fullFlinkUrl)}`
    
    // 上传文件
    const response = await fetch(uploadUrl, {
      method: 'POST',
      body: formData
    })
    
    const result = await response.json()
    
    if (result.success && result.data.files) {
      // 将文件添加到列表
      result.data.files.forEach(file => {
        tempFileList.value.push({
          name: file.filename,
          status: 'success',
          percentage: 100,
          selected: true,
          registering: false,
          size: file.size
        })
      })
      
      ElMessage.success({
        message: `成功上传 ${result.data.files.length} 个JAR文件到服务器临时存储，请点击"同步到Flink"完成部署`,
        duration: 5000
      })
    } else {
      ElMessage.error(result.error || '上传失败')
    }
  } catch (error) {
    componentLogger.error('上传目录文件失败:', error)
    ElMessage.error('上传失败')
  } finally {
    // 重置input，允许再次选择相同目录
    event.target.value = ''
  }
}

const handleUploadSuccess = (response, file, fileList) => {
  if (file && response.success) {
    file.status = 'success'
    file.percentage = 100
    file.selected = true // 默认选中
    file.registering = false // 初始化同步状态
    ElMessage.success({
      message: `${file.name} 已上传到服务器临时存储，请点击"同步到Flink"完成部署`,
      duration: 4000
    })
  } else {
    file.status = 'fail'
    file.selected = false
    ElMessage.error(`${file.name} 上传失败`)
  }
  
  // 同步更新 tempFileList
  tempFileList.value = fileList
}

// 移除文件
const removeFile = (index) => {
  tempFileList.value.splice(index, 1)
}

// 全选文件
const selectAllFiles = () => {
  tempFileList.value.forEach(file => {
    if (file.status === 'success') {
      file.selected = true
    }
  })
}

// 取消全选文件
const unselectAllFiles = () => {
  tempFileList.value.forEach(file => {
    file.selected = false
  })
}

// 清空临时文件列表
const clearTempFiles = async () => {
  try {
    await ElMessageBox.confirm(
      '确定要清空临时文件列表吗？这不会删除已同步到Flink的JAR包。',
      '确认清空',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }
    )
    
    tempFileList.value = []
    ElMessage.success('已清空临时文件列表')
  } catch (error) {
    // 用户取消
  }
}

// 轮询任务状态
const pollTaskStatus = async (taskId) => {
  const pollInterval = 1000 // 每秒轮询一次
  let isFirstPoll = true
  
  while (true) {
    try {
      const response = await api.get(`/jars/task/${taskId}`)
      
      if (!response.data.success) {
        throw new Error(response.data.error || '获取任务状态失败')
      }
      
      const task = response.data.data
      
      // 第一次轮询成功后，如果在后台运行，保存初始状态
      if (isFirstPoll && hasBackgroundTask.value) {
        console.log('[Jars] 首次轮询成功，保存初始状态')
        backgroundTaskProgress.value = `${task.current || 0}/${task.total || 0}`
        saveBackgroundTaskState()
        isFirstPoll = false
      }
      
      // 更新后台任务进度（如果在后台运行）
      if (hasBackgroundTask.value && !isFirstPoll) {
        backgroundTaskProgress.value = `${task.current || 0}/${task.total || 0}`
        // 保存更新后的进度
        saveBackgroundTaskState()
      }
      
      // 更新进度对话框（只有在对话框打开且引用有效时才更新）
      if (showBulkProgressDialog.value && bulkProgressRef.value) {
        try {
          // 获取对话框中的文件列表
          const dialogJobs = bulkProgressRef.value.jobResults || []
          
          // 根据文件名匹配并更新状态
          task.results.forEach((result) => {
            // 每次更新前都检查引用是否有效
            if (!bulkProgressRef.value) return
            
            // 在对话框中查找对应的文件索引
            const dialogIndex = dialogJobs.findIndex(job => job.name === result.filename)
            if (dialogIndex === -1) return // 找不到对应文件，跳过
            
            if (result.success) {
              bulkProgressRef.value.markSuccess(dialogIndex)
            } else if (result.cancelled || (task.status === 'cancelled' && result.success === null)) {
              // 任务被取消，标记为取消
              bulkProgressRef.value.markError(dialogIndex, result.message || '任务已取消')
            } else if (result.error || result.message) {
              // 有错误信息，标记为失败
              bulkProgressRef.value.markError(dialogIndex, result.message || '上传失败')
            }
          })
        } catch (error) {
          console.warn('[Jars] 更新进度对话框失败，可能对话框已关闭:', error)
        }
      }
      
      // 检查任务是否完成
      if (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled') {
        // 注意：不在这里显示通知，全局 Store 会显示
        
        // 从临时列表中移除成功上传的文件
        if (task.status === 'completed' || task.status === 'cancelled') {
          task.results.forEach(result => {
            if (result.success) {
              const index = tempFileList.value.findIndex(f => f.name === result.filename)
              if (index !== -1) {
                tempFileList.value.splice(index, 1)
              }
            }
          })
        }
        
        // 标记进度对话框为完成（只有在对话框打开时才更新）
        if (showBulkProgressDialog.value && bulkProgressRef.value) {
          try {
            bulkProgressRef.value.complete()
          } catch (error) {
            console.warn('[Jars] 标记对话框完成失败，可能对话框已关闭:', error)
          }
        }
        
        // 刷新JAR列表
        fetchJars()
        
        // 清除任务ID和文件列表
        currentTaskId.value = null
        currentTaskFiles.value = []
        
        break
      }
      
      // 等待下一次轮询
      await new Promise(resolve => setTimeout(resolve, pollInterval))
      
    } catch (error) {
      componentLogger.error('轮询任务状态失败:', error)
      ElMessage.error(`轮询任务状态失败: ${error.message}`)
      break
    }
  }
  
  // 标记后台任务完成
  if (hasBackgroundTask.value) {
    backgroundTaskCompleted.value = true
    // 保存完成状态
    saveBackgroundTaskState()
    // 3秒后自动隐藏后台任务按钮并清除状态
    setTimeout(() => {
      hasBackgroundTask.value = false
      localStorage.removeItem('jars_background_task')
    }, 3000)
  }
}

// 提交到Flink（使用新的异步API）
const submitToFlink = async () => {
  if (tempFileList.value.length === 0) {
    ElMessage.warning('请先上传文件到服务器')
    return
  }
  
  if (!uploadForm.value.flinkEnv) {
    ElMessage.warning('请选择Flink环境')
    return
  }
  
  // 获取选中的成功上传的文件
  const selectedFiles = tempFileList.value.filter(file => 
    file.selected && file.status === 'success'
  )
  
  if (selectedFiles.length === 0) {
    ElMessage.warning('请选择要同步的文件')
    return
  }
  
  // 如果已有后台任务在运行，提示用户
  if (hasBackgroundTask.value) {
    ElMessage.warning('已有同步任务在后台运行中')
    return
  }
  
  // 关闭上传对话框
  showUploadDialog.value = false
  
  // 打开进度对话框
  showBulkProgressDialog.value = true
  
  // 等待下一帧，确保对话框已渲染
  await nextTick()
  
  // 初始化进度对话框
  const fileItems = selectedFiles.map(file => ({ job_name: file.name }))
  bulkProgressRef.value.initProgress(fileItems)
  
  // 保存文件列表，用于重新打开对话框
  currentTaskFiles.value = selectedFiles.map(file => file.name)
  
  try {
    // 构建完整的Flink环境URL
    const envConfig = appStore.flinkEnvironments.find(env => env.name === uploadForm.value.flinkEnv)
    let fullFlinkUrl = uploadForm.value.flinkEnv
    if (envConfig && envConfig.host) {
      const protocol = envConfig.protocol || 'http'
      const host = envConfig.host || ''
      const port = envConfig.port ? `:${envConfig.port}` : ''
      const path = envConfig.path || ''
      fullFlinkUrl = `${protocol}://${host}${port}${path}`
    }
    
    // 调用异步批量上传API
    const response = await api.post('/jars/upload-batch-async', {
      filenames: selectedFiles.map(f => f.name),
      flinkEnv: fullFlinkUrl,
      cleanFirst: uploadForm.value.cleanFirst
    })
    
    if (!response.data.success) {
      throw new Error(response.data.error || '启动批量上传任务失败')
    }
    
    const taskId = response.data.data.task_id
    currentTaskId.value = taskId  // 保存任务ID，用于取消
    
    console.log('[Jars] 任务已启动，taskId:', taskId)
    console.log('[Jars] currentTaskId.value 已设置:', currentTaskId.value)
    
    // 添加到全局后台任务 Store
    backgroundTaskStore.addTask({
      taskId: taskId,
      type: 'jar-upload',
      files: currentTaskFiles.value
    })
    
    // 轮询任务状态（本地轮询，用于更新进度对话框）
    await pollTaskStatus(taskId)
    
  } catch (error) {
    componentLogger.error('批量上传失败:', error)
    ElMessage.error(`批量上传失败: ${error.message}`)
    if (showBulkProgressDialog.value && bulkProgressRef.value) {
      try {
        bulkProgressRef.value.complete()
      } catch (e) {
        console.warn('[Jars] 标记对话框完成失败，可能对话框已关闭:', e)
      }
    }
  }
  
  // 标记后台任务完成
  if (hasBackgroundTask.value) {
    backgroundTaskCompleted.value = true
    // 保存完成状态
    saveBackgroundTaskState()
    // 3秒后自动隐藏后台任务按钮并清除状态
    setTimeout(() => {
      hasBackgroundTask.value = false
      localStorage.removeItem('jars_background_task')
    }, 3000)
  }
}

const handleUploadError = (error, file, fileList) => {
  // 上传失败时设置正确的状态
  if (file) {
    file.status = 'fail'
    file.percentage = 0
  }
  
  ElMessage.error(`${file.name} 上传失败`)
}

const handleUploadProgress = (event, file, fileList) => {
  // 第一阶段：文件上传到后端服务 (0% → 50%)
  let progress = 0
  if (event && typeof event.percent === 'number') {
    progress = Math.min(50, Math.max(0, event.percent * 0.5)) // 上传到后端最多50%
  } else if (event && event.loaded && event.total) {
    // 如果percent不存在，手动计算
    progress = Math.min(50, Math.max(0, (event.loaded / event.total) * 50))
  }
  
  componentLogger.info(`Upload to backend progress for ${file.name}: ${progress.toFixed(1)}%`)
  
  // 手动更新文件对象的进度
  if (file && file.status === 'uploading') {
    file.percentage = progress
  }
}

// 取消上传任务
const cancelUploadTask = async () => {
  if (!currentTaskId.value) {
    ElMessage.warning('没有正在运行的任务')
    return
  }
  
  try {
    await ElMessageBox.confirm(
      '确定要取消正在进行的上传任务吗？已上传的文件不会回滚。',
      '确认取消',
      {
        confirmButtonText: '确定取消',
        cancelButtonText: '继续上传',
        type: 'warning'
      }
    )
    
    // 调用取消API
    const response = await api.post(`/jars/task/${currentTaskId.value}/cancel`)
    
    if (response.data.success) {
      ElMessage.success('任务已取消')
    } else {
      ElMessage.error(response.data.error || '取消任务失败')
    }
  } catch (error) {
    if (error !== 'cancel') {
      componentLogger.error('取消任务失败:', error)
      ElMessage.error('取消任务失败')
    }
    // 用户点击"继续上传"，不做任何操作
  }
}

// 处理后台任务
const handleBackgroundTask = (progress) => {
  console.log('[Jars] handleBackgroundTask 被调用')
  console.log('[Jars] 接收到的进度信息:', progress)
  console.log('[Jars] 设置前 hasBackgroundTask.value =', hasBackgroundTask.value)
  
  componentLogger.info('后台任务触发:', progress)
  hasBackgroundTask.value = true
  // 如果 current 为 0，显示 "0/总数"，否则显示实际进度
  backgroundTaskProgress.value = `${progress.current || 0}/${progress.total || 0}`
  backgroundTaskCompleted.value = false
  
  console.log('[Jars] 设置后 hasBackgroundTask.value =', hasBackgroundTask.value)
  console.log('[Jars] backgroundTaskProgress.value =', backgroundTaskProgress.value)
  componentLogger.info('hasBackgroundTask 设置为 true，进度:', backgroundTaskProgress.value)
  
  // 保存状态到 localStorage
  saveBackgroundTaskState()
}

// 处理单击事件（延迟执行，给双击留时间）
const handleSingleClick = () => {
  // 清除之前的定时器
  if (clickTimer.value) {
    clearTimeout(clickTimer.value)
  }
  
  // 延迟300ms执行收起操作
  clickTimer.value = setTimeout(() => {
    isSidebarExpanded.value = false
    clickTimer.value = null
  }, 300)
}

// 处理双击事件
const handleDoubleClick = () => {
  // 取消单击的延迟执行
  if (clickTimer.value) {
    clearTimeout(clickTimer.value)
    clickTimer.value = null
  }
  
  // 立即打开进度对话框
  reopenProgressDialog()
}

// 重新打开进度对话框
const reopenProgressDialog = async () => {
  console.log('[Jars] 重新打开进度对话框')
  console.log('[Jars] currentTaskId:', currentTaskId.value)
  console.log('[Jars] currentTaskFiles:', currentTaskFiles.value)
  
  // 直接打开进度对话框
  showBulkProgressDialog.value = true
  
  // 等待对话框渲染完成
  await nextTick()
  
  // 如果有当前任务，重新初始化对话框内容
  if (currentTaskId.value && bulkProgressRef.value) {
    try {
      // 获取当前任务状态
      const response = await api.get(`/jars/task/${currentTaskId.value}`)
      if (!response.data.success) {
        ElMessage.error('获取任务状态失败')
        return
      }
      
      const task = response.data.data
      console.log('[Jars] 获取到任务状态:', task)
      
      // 如果本地没有文件列表，从API结果中提取
      let fileList = currentTaskFiles.value
      if (!fileList || fileList.length === 0) {
        console.log('[Jars] 本地无文件列表，从API结果提取')
        fileList = task.results.map(result => result.filename)
        currentTaskFiles.value = fileList
        console.log('[Jars] 从API提取到', fileList.length, '个文件')
      }
      
      // 使用文件列表初始化对话框
      const fileItems = fileList.map(filename => ({ job_name: filename }))
      bulkProgressRef.value.initProgress(fileItems)
      console.log('[Jars] 初始化对话框，共', fileList.length, '个文件')
      
      // 根据文件名匹配并更新状态
      const dialogJobs = bulkProgressRef.value.jobResults || []
      task.results.forEach((result) => {
        // 在对话框中查找对应的文件索引
        const dialogIndex = dialogJobs.findIndex(job => job.name === result.filename)
        if (dialogIndex === -1) {
          console.warn('[Jars] 找不到文件:', result.filename)
          return
        }
        
        if (result.success) {
          bulkProgressRef.value.markSuccess(dialogIndex)
        } else if (result.cancelled || (task.status === 'cancelled' && result.success === null)) {
          bulkProgressRef.value.markError(dialogIndex, result.message || '任务已取消')
        } else if (result.error || result.message) {
          bulkProgressRef.value.markError(dialogIndex, result.message || '上传失败')
        }
      })
      
      console.log('[Jars] 对话框内容已初始化，已更新', task.results.length, '个文件状态')
      
      // 如果任务已完成，标记对话框为完成
      if (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled') {
        bulkProgressRef.value.complete()
      }
    } catch (error) {
      console.error('[Jars] 初始化对话框失败:', error)
      ElMessage.error('初始化对话框失败')
    }
  } else {
    console.warn('[Jars] 无法初始化对话框 - currentTaskId:', currentTaskId.value, 'bulkProgressRef:', !!bulkProgressRef.value)
  }
  
  console.log('[Jars] 对话框已打开')
}

// 水平拖动相关函数
const startDragX = (e) => {
  // 记录开始时间和位置
  dragStartTime.value = Date.now()
  dragStartX.value = e.clientX
  hasMoved.value = false
  
  // 延迟判断是否真的要拖动（避免与双击冲突）
  setTimeout(() => {
    const timeSinceStart = Date.now() - dragStartTime.value
    // 如果超过150ms还按着，认为是拖动
    if (timeSinceStart >= 150 && !hasMoved.value) {
      isDraggingX.value = true
      document.addEventListener('mousemove', onDragX)
      document.addEventListener('mouseup', stopDragX)
    }
  }, 150)
  
  // 立即添加 mouseup 监听，以便取消拖动
  document.addEventListener('mouseup', stopDragX)
}

const onDragX = (e) => {
  if (!isDraggingX.value) {
    // 即使还没开始拖动，也检测鼠标移动
    const moved = Math.abs(e.clientX - dragStartX.value) > 5
    if (moved) {
      hasMoved.value = true
      isDraggingX.value = true
    } else {
      return
    }
  }
  
  const deltaX = dragStartX.value - e.clientX
  const newX = floatingButtonX.value + deltaX
  
  // 限制在合理范围内（距离右边 20px 到距离左边 20px）
  const maxX = window.innerWidth - 200 // 最多到左边
  const minX = 20 // 最少距离右边 20px
  
  floatingButtonX.value = Math.max(minX, Math.min(newX, maxX))
  dragStartX.value = e.clientX
  
  if (!document.querySelector('.background-task-indicator')) return
  document.querySelector('.background-task-indicator').style.cursor = 'grabbing'
}

const stopDragX = () => {
  isDraggingX.value = false
  hasMoved.value = false
  document.removeEventListener('mousemove', onDragX)
  document.removeEventListener('mouseup', stopDragX)
  
  if (document.querySelector('.background-task-indicator')) {
    document.querySelector('.background-task-indicator').style.cursor = 'grab'
  }
}

// 保存后台任务状态到 localStorage
const saveBackgroundTaskState = () => {
  if (hasBackgroundTask.value && currentTaskId.value) {
    const taskState = {
      taskId: currentTaskId.value,
      progress: backgroundTaskProgress.value,
      completed: backgroundTaskCompleted.value,
      files: currentTaskFiles.value, // 保存文件列表
      timestamp: Date.now()
    }
    localStorage.setItem('jars_background_task', JSON.stringify(taskState))
    console.log('[Jars] 保存后台任务状态:', taskState)
  } else {
    console.log('[Jars] 无法保存状态 - hasBackgroundTask:', hasBackgroundTask.value, 'currentTaskId:', currentTaskId.value)
    if (!currentTaskId.value) {
      // 如果没有 taskId，不删除 localStorage，可能是还没设置
      console.log('[Jars] taskId 未设置，跳过保存')
    } else {
      localStorage.removeItem('jars_background_task')
    }
  }
}

// 从 localStorage 恢复后台任务状态
const restoreBackgroundTaskState = async () => {
  const savedState = localStorage.getItem('jars_background_task')
  if (!savedState) {
    console.log('[Jars] 没有保存的后台任务')
    return
  }
  
  try {
    const taskState = JSON.parse(savedState)
    console.log('[Jars] 恢复后台任务状态:', taskState)
    
    // 检查任务是否过期（超过24小时）
    const age = Date.now() - taskState.timestamp
    if (age > 24 * 60 * 60 * 1000) {
      console.log('[Jars] 任务已过期，清除状态')
      localStorage.removeItem('jars_background_task')
      return
    }
    
    // 恢复任务状态
    currentTaskId.value = taskState.taskId
    hasBackgroundTask.value = true
    backgroundTaskProgress.value = taskState.progress
    backgroundTaskCompleted.value = taskState.completed
    currentTaskFiles.value = taskState.files || [] // 恢复文件列表
    
    console.log('[Jars] 任务状态已恢复，文件数量:', currentTaskFiles.value.length)
    console.log('[Jars] 开始轮询任务:', taskState.taskId)
    
    // 如果任务未完成，继续轮询
    if (!taskState.completed) {
      await pollTaskStatus(taskState.taskId)
    }
  } catch (error) {
    console.error('[Jars] 恢复任务状态失败:', error)
    localStorage.removeItem('jars_background_task')
  }
}

// 生命周期
onMounted(async () => {
  console.log('[Jars] 组件已挂载')
  console.log('[Jars] 初始 hasBackgroundTask.value =', hasBackgroundTask.value)
  
  // 暴露调试方法到 window 对象（仅用于开发调试）
  if (import.meta.env.DEV) {
    window.jarsDebug = {
      // 显示浮动框
      showFloatingButton: () => {
        hasBackgroundTask.value = true
        backgroundTaskProgress.value = '5/22'
        backgroundTaskCompleted.value = false
        console.log('[Jars Debug] 浮动框已显示')
      },
      // 隐藏浮动框
      hideFloatingButton: () => {
        hasBackgroundTask.value = false
        console.log('[Jars Debug] 浮动框已隐藏')
      },
      // 标记任务完成
      completeTask: () => {
        backgroundTaskCompleted.value = true
        backgroundTaskProgress.value = '22/22'
        console.log('[Jars Debug] 任务已标记为完成')
      },
      // 打开进度对话框
      openDialog: () => {
        showBulkProgressDialog.value = true
        console.log('[Jars Debug] 进度对话框已打开')
      },
      // 查看当前状态
      getState: () => {
        return {
          hasBackgroundTask: hasBackgroundTask.value,
          backgroundTaskProgress: backgroundTaskProgress.value,
          backgroundTaskCompleted: backgroundTaskCompleted.value,
          currentTaskId: currentTaskId.value,
          showBulkProgressDialog: showBulkProgressDialog.value
        }
      }
    }
    console.log('[Jars Debug] 调试方法已挂载到 window.jarsDebug')
    console.log('[Jars Debug] 可用命令:')
    console.log('  - window.jarsDebug.showFloatingButton()  // 显示浮动框')
    console.log('  - window.jarsDebug.hideFloatingButton()  // 隐藏浮动框')
    console.log('  - window.jarsDebug.completeTask()        // 标记任务完成')
    console.log('  - window.jarsDebug.openDialog()          // 打开进度对话框')
    console.log('  - window.jarsDebug.getState()            // 查看当前状态')
  }
  
  // 确保app store已初始化
  if (appStore.flinkEnvironments.length === 0) {
    await appStore.loadFlinkEnvironments()
  }
  
  // 恢复后台任务状态
  await restoreBackgroundTaskState()
  
  fetchJars()
})
</script>

<style scoped>
.jars-container {
  padding: 0;
}

.page-header {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding: 20px 24px;
  border-radius: 12px;
  overflow: hidden;
  color: #303133;
  min-height: 120px;
  border: 1px solid #e4e7ed;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.header-background {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
}

.bg-gradient {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
  opacity: 1;
}

.bg-pattern {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: 
    radial-gradient(circle at 20% 80%, rgba(64, 158, 255, 0.05) 0%, transparent 50%),
    radial-gradient(circle at 80% 20%, rgba(64, 158, 255, 0.05) 0%, transparent 50%);
  animation: patternMove 30s ease-in-out infinite;
}

@keyframes patternMove {
  0%, 100% { transform: translateX(0) translateY(0); }
  25% { transform: translateX(-10px) translateY(-5px); }
  50% { transform: translateX(5px) translateY(-10px); }
  75% { transform: translateX(-5px) translateY(5px); }
}

.page-title {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 24px;
  flex: 1;
}

.title-icon {
  position: relative;
}

.page-header .title-icon .icon-container {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px !important;
  height: 36px !important;
  background: linear-gradient(135deg, #409eff 0%, #66b3ff 100%) !important;
  border-radius: 8px !important;
  color: white !important;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2) !important;
}

.page-header .title-icon .icon-container:hover {
  transform: scale(1.05);
  box-shadow: 0 4px 16px rgba(64, 158, 255, 0.3) !important;
}

.page-header .title-icon .icon-glow {
  position: absolute;
  top: -4px;
  left: -4px;
  right: -4px;
  bottom: -4px;
  background: linear-gradient(45deg, rgba(64, 158, 255, 0.2), rgba(102, 179, 255, 0.3));
  border-radius: 12px;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.page-header .title-icon .icon-container:hover .icon-glow {
  opacity: 1;
}

.title-content {
  flex: 1;
}

.title-main {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.title-main h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 600;
  color: #303133;
  letter-spacing: -0.3px;
}

.title-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 12px;
  background: linear-gradient(135deg, #409eff 0%, #66b3ff 100%);
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  color: white;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.25);
}

.page-description {
  margin: 0 0 12px 0;
  color: #606266;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  display: flex;
  align-items: center;
  gap: 8px;
}

.page-description .el-icon {
  font-size: 16px;
  color: #909399;
}

.title-meta {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.meta-item {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #909399;
  font-size: 12px;
  font-weight: 500;
}

.page-stats {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 20px;
}

.stat-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: #fff;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  min-width: 120px;
  position: relative;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.stat-card:hover {
  background: #fff;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  border-color: #409eff;
}

.stat-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  font-size: 16px;
  transition: all 0.3s ease;
}

.stat-card.total .stat-icon {
  background: rgba(64, 158, 255, 0.1);
  color: #409eff;
}

.stat-card.uploaded .stat-icon {
  background: rgba(103, 194, 58, 0.1);
  color: #67c23a;
}

.stat-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.stat-number {
  font-size: 20px;
  font-weight: 700;
  color: #303133;
  line-height: 1;
}

.stat-label {
  font-size: 12px;
  color: #606266;
  font-weight: 500;
}

.stat-trend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  color: #909399;
  font-weight: 500;
}

.search-section {
  margin-bottom: 24px;
}

.search-card {
  border-radius: 16px;
  border: none;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  transition: all 0.3s ease;
}

.search-card:hover {
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  transform: translateY(-2px);
}

.search-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.search-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}

.search-title .el-icon {
  color: #409eff;
}

.instance-selector-compact {
  display: flex;
  align-items: center;
  gap: 12px;
}

.instance-selector-compact .el-tag {
  font-weight: 500;
}

.search-form {
  margin: 0;
}

.search-form-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
}

.search-form-fields {
  display: flex;
  gap: 16px;
  flex: 1;
}

.search-form-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

.search-btn {
  background: linear-gradient(135deg, #409EFF 0%, #66B3FF 100%);
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 500;
}

.search-btn:hover {
  background: linear-gradient(135deg, #66B3FF 0%, #409EFF 100%);
  box-shadow: 0 6px 20px rgba(64, 158, 255, 0.4);
  transform: translateY(-2px);
}

.reset-btn {
  background: linear-gradient(135deg, #E6A23C 0%, #F0C78A 100%);
  border: none;
  border-radius: 8px;
  color: #fff;
  box-shadow: 0 4px 12px rgba(230, 162, 60, 0.3);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 500;
}

.reset-btn:hover {
  background: linear-gradient(135deg, #F0C78A 0%, #E6A23C 100%);
  box-shadow: 0 6px 20px rgba(230, 162, 60, 0.4);
  transform: translateY(-2px);
  color: #fff;
}

.upload-btn {
  background: linear-gradient(135deg, #67C23A 0%, #85CE61 100%);
  border: none;
  border-radius: 8px;
  color: #fff;
  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 500;
}

.upload-btn:hover {
  background: linear-gradient(135deg, #85CE61 0%, #67C23A 100%);
  box-shadow: 0 6px 20px rgba(103, 194, 58, 0.4);
  transform: translateY(-2px);
  color: #fff;
}

.refresh-btn {
  background: linear-gradient(135deg, #67C23A 0%, #85CE61 100%);
  border: none;
  border-radius: 8px;
  color: #fff;
  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 500;
}

.refresh-btn:hover {
  background: linear-gradient(135deg, #85CE61 0%, #67C23A 100%);
  box-shadow: 0 6px 20px rgba(103, 194, 58, 0.4);
  transform: translateY(-2px);
  color: #fff;
}

/* 批量操作栏 - 参考独立批量操作示例 */
.batch-actions-bar {
  margin-bottom: 20px;
  padding: 12px 16px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  animation: slideInDown 0.3s ease;
}

.batch-info-compact {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: white;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
}

.batch-info-compact .selected-icon {
  font-size: 16px;
  color: white;
}

.batch-info-compact strong {
  font-weight: 600;
  margin: 0 2px;
}

.batch-divider {
  color: white;
  font-size: 16px;
  opacity: 0.6;
  margin: 0 4px;
}

.batch-buttons-compact {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.batch-btn-compact {
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  border: none;
  color: #fff;
  background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
}

.batch-btn-compact:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.batch-btn-compact .el-icon {
  margin-right: 4px;
  font-size: 16px;
}

@keyframes slideInDown {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.table-section {
  margin-bottom: 24px;
}

.table-card {
  border-radius: 16px;
  border: none;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  transition: all 0.3s ease;
}

.table-card:hover {
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}

.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.table-header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.table-info {
  display: flex;
  align-items: center;
}

.pagination-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid rgba(64, 158, 255, 0.3);
  background: rgba(64, 158, 255, 0.05);
  font-size: 12px;
  font-weight: 500;
  color: #409eff;
}

.pagination-pill .page-count {
  line-height: 1;
}

.table-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}

.table-title .el-icon {
  color: #409eff;
}

.table-info {
  display: flex;
  align-items: center;
  gap: 8px;
}

.table-container {
  width: 100%;
  overflow-x: visible;
  margin: 0 auto;
  border-radius: 12px;
  background: #fff;
}

/* 表格滚动条样式 */
.table-container::-webkit-scrollbar {
  height: 8px;
}

.table-container::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 4px;
}

.table-container::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 4px;
  transition: background-color 0.2s ease;
}

.table-container::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}

.enhanced-table {
  font-family: inherit;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}

/* 确保表格头部不换行 - 参考 StarRocks Tables 实现 */
.table-container .el-table {
  width: 100% !important;
  border-radius: 16px;
  overflow: hidden;
  background: #fff;
}

.table-container .el-table__header {
  background: #f8f9fa;
  border-bottom: 1px solid #e4e7ed;
}

.table-container .el-table th {
  background: transparent !important;
  border-bottom: none;
  padding: 6px 12px;
  vertical-align: middle;
}

/* 为表头设置相同的宽度 */
.table-container .el-table th:nth-child(1) { /* 选择框 */
  width: 55px;
}

.table-container .el-table th:nth-child(2) { /* JAR名称 */
  width: 20%;
  min-width: 150px;
}

.table-container .el-table th:nth-child(3) { /* JAR ID */
  width: 25%;
  min-width: 180px;
}

.table-container .el-table th:nth-child(4) { /* 入口类 */
  width: 25%;
  min-width: 180px;
}

.table-container .el-table th:nth-child(5) { /* 上传时间 */
  width: 20%;
  min-width: 160px;
}

.table-container .el-table th:nth-child(6) { /* 操作 */
  width: 10%;
  min-width: 120px;
  text-align: center;
}

/* 简化列标题样式 - 纯中文 */
.column-header-simple {
  font-size: 14px;
  font-weight: 500;
  color: #606266;
  text-align: left;
  padding: 0;
}

.table-container .el-table td {
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
  padding: 12px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
}

/* 表格行样式 */
.table-container :deep(.el-table__row td) {
  background: #fff !important;
}

.table-container :deep(.el-table__row:hover td) {
  background: rgba(64, 158, 255, 0.05) !important;
}

/* 为不同列设置合适的宽度 */
.table-container .el-table td:nth-child(1) { /* 选择框 */
  width: 55px;
}

.table-container .el-table td:nth-child(2) { /* JAR名称 */
  width: 20%;
  min-width: 150px;
}

.table-container .el-table td:nth-child(3) { /* JAR ID */
  width: 25%;
  min-width: 180px;
}

.table-container .el-table td:nth-child(4) { /* 入口类 */
  width: 25%;
  min-width: 180px;
}

.table-container .el-table td:nth-child(5) { /* 上传时间 */
  width: 20%;
  min-width: 160px;
}

.table-container .el-table td:nth-child(6) { /* 操作 */
  width: 10%;
  min-width: 120px;
  text-align: center;
}

.enhanced-row {
  position: relative;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.enhanced-row:hover {
  transform: translateX(4px);
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
  z-index: 1;
}

.jar-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 0;
}

.jar-name.copyable {
  padding: 8px 32px 8px 0;
  cursor: pointer;
  transition: all 0.3s ease;
  border-radius: 6px;
}

.jar-name.copyable:hover {
  background: rgba(64, 158, 255, 0.1);
  transform: scale(1.02);
}

.jar-info {
  flex: 1;
  min-width: 0;
  margin-right: 8px;
}

.jar-title {
  color: #303133;
  margin-bottom: 2px;
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.copyable-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 24px;
  padding: 4px 32px 4px 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  border-radius: 6px;
}

.copyable-field:hover {
  background: rgba(64, 158, 255, 0.08);
  transform: translateX(2px);
}

.copy-hint {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  opacity: 0;
  transition: all 0.3s ease;
  background: rgba(64, 158, 255, 0.9);
  color: white;
  padding: 4px 6px;
  border-radius: 4px;
  font-size: 12px;
  pointer-events: none;
  z-index: 10;
}

.copyable:hover .copy-hint,
.copyable-field:hover .copy-hint {
  opacity: 1;
  transform: translateY(-50%) translateX(-4px);
}

.copy-hint::before {
  content: '';
  position: absolute;
  right: -4px;
  top: 50%;
  transform: translateY(-50%);
  border: 4px solid transparent;
  border-left-color: rgba(64, 158, 255, 0.9);
}

.action-buttons {
  display: flex;
  gap: 12px;
  flex-wrap: nowrap;
  white-space: nowrap;
  justify-content: flex-start;
  align-items: center;
  min-width: 180px;
}

.action-buttons .el-button {
  font-weight: 500;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.action-buttons .el-button:hover {
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.action-buttons .el-button:active {
  transform: translateY(0) scale(0.98);
}

.action-buttons .el-button--primary {
  background: linear-gradient(135deg, #409EFF 0%, #66B3FF 100%);
  border: none;
}

.action-buttons .el-button--danger {
  background: linear-gradient(135deg, #F56C6C 0%, #F78989 100%);
  border: none;
}

.jar-upload-dialog .el-dialog__header {
  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
  border-radius: 12px 12px 0 0;
  padding: 20px 24px;
  border-bottom: 1px solid #e4e7ed;
}

.jar-upload-dialog .el-dialog__body {
  padding: 24px;
  background: #fff;
}

.jar-upload-dialog .el-dialog__footer {
  background: #f8f9fa;
  border-radius: 0 0 12px 12px;
  padding: 16px 24px;
  border-top: 1px solid #e4e7ed;
}

/* 文件上传和管理区域样式 */
.file-upload-section {
  margin-top: 20px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #e4e7ed;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  color: #303133;
}

.header-left .el-icon {
  color: #409eff;
}

.file-count {
  color: #409eff;
  font-weight: 500;
}

.header-actions {
  display: flex;
  gap: 8px;
}

.upload-area {
  margin-bottom: 16px;
}

.upload-tip {
  margin-top: 12px;
  font-size: 13px;
  color: #606266;
  display: flex;
  align-items: flex-start;
  gap: 6px;
}

.upload-tip .el-icon {
  color: #409eff;
  margin-top: 2px;
  flex-shrink: 0;
}

.files-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.file-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: #fff;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  transition: all 0.3s ease;
}

.file-item.uploading {
  border-color: #409eff;
  background: rgba(64, 158, 255, 0.05);
}

.file-item.success {
  border-color: #67c23a;
  background: rgba(103, 194, 58, 0.05);
}

.file-item.selected {
  border-color: #409eff;
  background: rgba(64, 158, 255, 0.1);
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}

.file-item.registering {
  border-color: #409eff;
  background: rgba(64, 158, 255, 0.1);
}

.file-checkbox {
  flex-shrink: 0;
}

.file-info {
  display: flex;
  align-items: center;
  flex: 1;
}

.file-info .el-icon {
  margin-right: 8px;
  color: #606266;
}

.file-name {
  flex: 1;
  margin-right: 12px;
  font-size: 14px;
  color: #303133;
}

.file-status .el-icon {
  margin-right: 0;
}

.file-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.dialog-header {
  display: flex;
  align-items: center;
  gap: 16px;
}

.header-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  background: linear-gradient(135deg, #409eff 0%, #66b3ff 100%);
  border-radius: 12px;
  color: white;
  font-size: 20px;
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}

.header-content h3 {
  margin: 0 0 4px 0;
  font-size: 18px;
  font-weight: 600;
  color: #303133;
}

.header-content p {
  margin: 0;
  font-size: 14px;
  color: #606266;
}

/* 上传环境选择区域 */
.upload-env-section {
  margin-bottom: 20px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.upload-form {
  margin: 0;
}

.upload-form .el-form-item {
  margin-bottom: 16px;
}

.upload-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.option-tip {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.option-tip .el-icon {
  color: #409eff;
}

.upload-dragger {
  width: 100%;
}

.upload-dragger .el-upload-dragger {
  width: 100%;
  height: 200px;
  border: 2px dashed #d9d9d9;
  border-radius: 12px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: all 0.3s ease;
}

.upload-dragger .el-upload-dragger:hover {
  border-color: #409eff;
  background-color: rgba(64, 158, 255, 0.05);
}

.delete-warning {
  text-align: center;
  padding: 20px;
}

.delete-warning h3 {
  margin: 16px 0;
  color: #f56c6c;
}

.delete-warning p {
  margin: 8px 0;
  line-height: 1.6;
}

.delete-warning strong {
  color: #f56c6c;
  font-weight: bold;
}

/* 环境选择器样式 */
.env-option {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.env-name {
  font-size: 14px;
  font-weight: 500;
  color: #303133;
}

.env-url {
  font-size: 12px;
  color: #909399;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}

/* 文件列表提示样式 */
.file-list-alert {
  margin-bottom: 16px;
  border-radius: 8px;
}

.file-list-alert :deep(.el-alert__title) {
  font-size: 15px;
  margin-bottom: 8px;
}

.file-list-alert :deep(.el-alert__description) {
  font-size: 13px;
  line-height: 1.6;
}

/* 后台上传提示样式 */
.background-upload-alert {
  margin-bottom: 24px;
  border-radius: 12px;
  animation: pulse-border 2s infinite;
}

@keyframes pulse-border {
  0%, 100% {
    border-color: #409eff;
  }
  50% {
    border-color: #66b3ff;
  }
}

.background-upload-info {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  font-weight: 500;
}

.background-upload-info span {
  flex: 1;
}

.background-upload-info .cancel-upload-btn {
  margin-left: auto;
}

.background-upload-info .is-loading {
  animation: rotate 1s linear infinite;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* 上传按钮组样式 */
.upload-buttons-group {
  display: flex;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.upload-buttons-group :deep(.el-upload) {
  display: inline-block;
}

.upload-tip {
  margin-top: 12px;
  padding: 16px;
  background: #f0f9ff;
  border: 1px solid #b3d8ff;
  border-radius: 8px;
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.upload-tip .el-icon {
  color: #409eff;
  font-size: 18px;
  margin-top: 2px;
  flex-shrink: 0;
}

.upload-tip .tip-content {
  flex: 1;
}

.upload-tip .tip-content p {
  margin: 0;
  padding: 4px 0;
  font-size: 13px;
  line-height: 1.6;
  color: #606266;
}

.upload-tip .tip-content p:first-child {
  font-size: 14px;
  color: #303133;
  margin-bottom: 8px;
}

.scan-tip .el-icon {
  color: #409eff;
  margin-top: 2px;
  flex-shrink: 0;
}

/* 后台任务浮动按钮 - 右侧边栏样式 */
.background-task-indicator {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 12px;
  background: linear-gradient(180deg, rgba(64, 158, 255, 0.95) 0%, rgba(102, 179, 255, 0.95) 100%);
  border-radius: 8px 0 0 8px;
  box-shadow: -4px 0 16px rgba(64, 158, 255, 0.4);
  cursor: pointer;
  transition: all 0.3s ease;
  min-width: 60px;
  overflow: hidden;
}

.background-task-indicator:hover {
  box-shadow: -6px 0 20px rgba(64, 158, 255, 0.6);
  transform: translateY(-50%) translateX(-4px);
}

.background-task-indicator:active {
  transform: translateY(-50%) translateX(-2px);
}

/* 收起状态 */
.background-task-indicator.collapsed {
  min-width: 32px;
  padding: 12px 8px;
}

.background-task-indicator.collapsed:hover {
  transform: translateY(-50%) translateX(-6px);
}

/* 箭头图标 */
.arrow-icon {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 18px;
  animation: pulse-arrow 2s ease-in-out infinite;
  cursor: pointer;
}

/* 展开内容包装器 */
.task-content-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

@keyframes pulse-arrow {
  0%, 100% { 
    opacity: 1;
    transform: translateX(0);
  }
  50% { 
    opacity: 0.7;
    transform: translateX(-3px);
  }
}

.task-icon {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  color: white;
  font-size: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.icon-success {
  animation: pulse 2s ease-in-out infinite;
}

.icon-loading {
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

.task-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  writing-mode: vertical-rl;
  text-orientation: upright;
}

.task-label {
  font-size: 12px;
  font-weight: 600;
  color: white;
  letter-spacing: 2px;
  line-height: 1;
}

.task-progress {
  font-size: 11px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.9);
  letter-spacing: 1px;
  line-height: 1;
}

/* 滑动淡入淡出动画 */
.slide-fade-enter-active {
  transition: all 0.4s ease;
}

.slide-fade-leave-active {
  transition: all 0.3s ease;
}

.slide-fade-enter-from {
  opacity: 0;
  transform: translateY(-50%) translateX(100%);
}

.slide-fade-leave-to {
  opacity: 0;
  transform: translateY(-50%) translateX(100%);
}

.fade-leave-to {
  opacity: 0;
  transform: translateY(20px) scale(0.8);
}

/* 响应式设计 */
@media (max-width: 768px) {
  .page-header {
    flex-direction: column;
    gap: 16px;
  }
  
  .search-form-content {
    flex-direction: column;
    gap: 16px;
  }
  
  .search-form-fields {
    flex-direction: column;
    gap: 12px;
  }
  
  .action-buttons {
    flex-direction: column;
  }
}
</style>
